"""
Basic data structure used for general trading function in VN Trader.
"""

from dataclasses import dataclass
from datetime import datetime, date
from logging import INFO
from ctypes import Structure, c_char, c_double, c_longlong, Union, c_int

from .constant import Direction, Exchange, Interval, Offset, Status, Product, OptionType, OrderType

ACTIVE_STATUSES = set([Status.SUBMITTING, Status.NOTTRADED, Status.PARTTRADED])


class CTickData(Structure):
    _pack_ = 4
    _fields_ = [
        ("symbol", c_char * 12),
        ("exchange", c_char * 12),
        ("trading_day", c_int),
        ("name", c_char * 12),
        ("volume", c_double),
        ("open_interest", c_double),
        ("last_price", c_double),
        ("last_volume", c_double),
        ("limit_up", c_double),
        ("limit_down", c_double),

        ("open_price", c_double),
        ("high_price", c_double),
        ("low_price", c_double),
        ("pre_close", c_double),

        ("bid_price_1", c_double),
        ("bid_price_2", c_double),
        ("bid_price_3", c_double),
        ("bid_price_4", c_double),
        ("bid_price_5", c_double),

        ("ask_price_1", c_double),
        ("ask_price_2", c_double),
        ("ask_price_3", c_double),
        ("ask_price_4", c_double),
        ("ask_price_5", c_double),

        ("bid_volume_1", c_double),
        ("bid_volume_2", c_double),
        ("bid_volume_3", c_double),
        ("bid_volume_4", c_double),
        ("bid_volume_5", c_double),

        ("ask_volume_1", c_double),
        ("ask_volume_2", c_double),
        ("ask_volume_3", c_double),
        ("ask_volume_4", c_double),
        ("ask_volume_5", c_double),

        ("timestamp", c_double),
        ("record_ts", c_double),
        ("vt_symbol", c_char * 24),
        ("gateway_name", c_char * 24)
    ]

class CBarData(Structure):
    _pack_ = 4
    _fields_ = [
        ("symbol", c_char * 12),
        ("exchange", c_char * 12),
        ("trading_day", c_int),
        ("interval", c_char * 12),
        ("volume", c_double),
        ("open_interest", c_double),
        ("open_price", c_double),
        ("high_price", c_double),
        ("low_price", c_double),
        ("close_price", c_double),
        ("turnover", c_double),

        ("timestamp", c_double),
        ("vt_symbol", c_char * 24),
        ("gateway_name", c_char * 24)
    ]

class CArbBarData(Structure):
    _pack_ = 4
    _fields_ = [
        ("symbol1", c_char * 12),
        ("symbol2", c_char * 12),
        ("exchange1", c_char * 12),
        ("exchange2", c_char * 12),
        ("trading_day", c_int),
        ("interval", c_char * 12),
        ("bid_ticks", c_int),
        ("ask_ticks", c_int),
        ("mid_ticks", c_int),
        ("bid_open_price", c_double),
        ("bid_high_price", c_double),
        ("bid_low_price", c_double),
        ("bid_close_price", c_double),
        ("ask_open_price", c_double),
        ("ask_high_price", c_double),
        ("ask_low_price", c_double),
        ("ask_close_price", c_double),
        ("mid_open_price", c_double),
        ("mid_high_price", c_double),
        ("mid_low_price", c_double),
        ("mid_close_price", c_double),

        ("timestamp", c_double),
        ("vt_symbol1", c_char * 24),
        ("vt_symbol2", c_char * 24),
        ("gateway_name", c_char * 24)
    ]

class CTickBarUnion(Union):
    _pack_ = 4
    _fields_ = [
        ("tick", CTickData),
        ("bar", CBarData),
        ("arbBar", CArbBarData)
    ]

class CTickBarData(Structure):
    _pack_ = 4
    _fields_ = [
        ("type", c_char * 8),
        ("data", CTickBarUnion)
    ]

@dataclass
class BaseData:
    """
    Any data object needs a gateway_name as source 
    and should inherit base data.
    """

    gateway_name: str

@dataclass
class TickData(BaseData):
    """
    Tick data contains information about:
        * last trade in market
        * orderbook snapshot
        * intraday market statistics.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime
    trading_day: date

    name: str = ""
    volume: float = 0
    open_interest: float = 0
    last_price: float = 0
    last_volume: float = 0
    limit_up: float = 0
    limit_down: float = 0

    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    pre_close: float = 0

    bid_price_1: float = 0
    bid_price_2: float = 0
    bid_price_3: float = 0
    bid_price_4: float = 0
    bid_price_5: float = 0

    ask_price_1: float = 0
    ask_price_2: float = 0
    ask_price_3: float = 0
    ask_price_4: float = 0
    ask_price_5: float = 0

    bid_volume_1: float = 0
    bid_volume_2: float = 0
    bid_volume_3: float = 0
    bid_volume_4: float = 0
    bid_volume_5: float = 0

    ask_volume_1: float = 0
    ask_volume_2: float = 0
    ask_volume_3: float = 0
    ask_volume_4: float = 0
    ask_volume_5: float = 0
    timestamp: int = 0

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"

    def to_ctickdata(self, ctick : CTickBarData):
        ctick.type = b"tick"
        ctick.data.tick.symbol = self.symbol.encode()
        ctick.data.tick.exchange = self.exchange.value.encode()
        # ctick.data.tick.trading_day = self.trading_day.strftime("%Y%m%d").encode()
        ctick.data.tick.trading_day = int(self.trading_day.strftime("%Y%m%d"))
        ctick.data.tick.name = self.name.encode()
        ctick.data.tick.volume = self.volume
        ctick.data.tick.open_interest = self.open_interest
        ctick.data.tick.last_price = self.last_price
        ctick.data.tick.last_volume = self.last_volume
        ctick.data.tick.limit_up = self.limit_up
        ctick.data.tick.limit_down = self.limit_down
        ctick.data.tick.open_price = self.open_price
        ctick.data.tick.high_price = self.high_price
        ctick.data.tick.low_price = self.low_price
        ctick.data.tick.pre_close = self.pre_close

        ctick.data.tick.bid_price_1 = self.bid_price_1
        ctick.data.tick.bid_price_2 = self.bid_price_2
        ctick.data.tick.bid_price_3 = self.bid_price_3
        ctick.data.tick.bid_price_4 = self.bid_price_4
        ctick.data.tick.bid_price_5 = self.bid_price_5

        ctick.data.tick.ask_price_1 = self.ask_price_1
        ctick.data.tick.ask_price_2 = self.ask_price_2
        ctick.data.tick.ask_price_3 = self.ask_price_3
        ctick.data.tick.ask_price_4 = self.ask_price_4
        ctick.data.tick.ask_price_5 = self.ask_price_5

        ctick.data.tick.bid_volume_1 = self.bid_volume_1
        ctick.data.tick.bid_volume_2 = self.bid_volume_2
        ctick.data.tick.bid_volume_3 = self.bid_volume_3
        ctick.data.tick.bid_volume_4 = self.bid_volume_4
        ctick.data.tick.bid_volume_5 = self.bid_volume_5

        ctick.data.tick.ask_volume_1 = self.ask_volume_1
        ctick.data.tick.ask_volume_2 = self.ask_volume_2
        ctick.data.tick.ask_volume_3 = self.ask_volume_3
        ctick.data.tick.ask_volume_4 = self.ask_volume_4
        ctick.data.tick.ask_volume_5 = self.ask_volume_5

        ctick.data.tick.timestamp  = self.datetime.timestamp()
        ctick.data.tick.record_ts = self.timestamp
        ctick.data.tick.vt_symbol = self.vt_symbol.encode()
        ctick.data.tick.gateway_name = self.gateway_name.encode()



@dataclass
class BarData(BaseData):
    """
    Candlestick bar data of a certain trading period.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime
    trading_day: date

    interval: Interval = None
    volume: float = 0
    open_interest: float = 0
    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    close_price: float = 0
    turnover: float = 0

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"

    def to_cbar(self, cbar : CTickBarData):
        cbar.type = b"bar"
        cbar.data.bar.symbol = self.symbol.encode()
        cbar.data.bar.exchange = self.exchange.value.encode()
        cbar.data.bar.timestamp = self.datetime.timestamp()
        # cbar.data.bar.trading_day = self.trading_day.strftime("%Y%m%d").encode()
        cbar.data.bar.trading_day = int(self.trading_day.strftime("%Y%m%d"))
        cbar.data.bar.interval = self.interval.value.encode()
        cbar.data.bar.volume = self.volume
        cbar.data.bar.open_interest = self.open_interest
        cbar.data.bar.open_price = self.open_price
        cbar.data.bar.high_price = self.high_price
        cbar.data.bar.low_price = self.low_price
        cbar.data.bar.close_price = self.close_price
        cbar.data.bar.turnover = self.turnover
        cbar.data.bar.vt_symbol = self.vt_symbol.encode()
        cbar.data.bar.gateway_name = self.gateway_name.encode()


@dataclass
class OrderData(BaseData):
    """
    Order data contains information for tracking lastest status 
    of a specific order.
    """

    symbol: str
    exchange: Exchange
    orderid: str

    type: OrderType = OrderType.LIMIT
    direction: Direction = ""
    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    traded: float = 0
    status: Status = Status.SUBMITTING
    time: str = ""

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
        self.vt_orderid = f"{self.gateway_name}.{self.orderid}"

    def is_active(self):
        """
        Check if the order is active.
        """
        if self.status in ACTIVE_STATUSES:
            return True
        else:
            return False

    def create_cancel_request(self):
        """
        Create cancel request object from order.
        """
        req = CancelRequest(
            orderid=self.orderid, symbol=self.symbol, exchange=self.exchange
        )
        return req


@dataclass
class TradeData(BaseData):
    """
    Trade data contains information of a fill of an order. One order
    can have several trade fills.
    """

    symbol: str
    exchange: Exchange
    orderid: str
    tradeid: str
    datetime: datetime
    trading_day: int
    direction: Direction = ""

    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    time: str = ""
    sub_strategy_id: int = None

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
        self.vt_orderid = f"{self.gateway_name}.{self.orderid}"
        self.vt_tradeid = f"{self.gateway_name}.{self.tradeid}"

    @staticmethod
    def from_dict(input_dict):
        import copy
        v = copy.deepcopy(input_dict)
        v['direction'] = Direction[v['direction']]
        v['offset'] = Offset[v['offset']]
        v['exchange'] = Exchange[v['exchange']]
        v['datetime'] = datetime.strptime(v['datetime'], "%Y%m%d %H:%M:%S.%f")
        v['sub_strategy_id'] = v['sub_sid']
        del v['sub_sid']
        return TradeData(**v)


@dataclass
class PositionData(BaseData):
    """
    Positon data is used for tracking each individual position holding.
    """

    symbol: str
    exchange: Exchange
    direction: Direction

    volume: float = 0
    frozen: float = 0
    price: float = 0
    pnl: float = 0
    yd_volume: float = 0

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
        self.vt_positionid = f"{self.vt_symbol}.{self.direction.value}"


@dataclass
class AccountData(BaseData):
    """
    Account data contains information about balance, frozen and
    available.
    """

    accountid: str

    balance: float = 0
    frozen: float = 0

    def __post_init__(self):
        """"""
        self.available = self.balance - self.frozen
        self.vt_accountid = f"{self.gateway_name}.{self.accountid}"


@dataclass
class LogData(BaseData):
    """
    Log data is used for recording log messages on GUI or in log files.
    """

    msg: str
    level: int = INFO

    def __post_init__(self):
        """"""
        self.time = datetime.now()


@dataclass
class ContractData(BaseData):
    """
    Contract data contains basic information about each contract traded.
    """

    symbol: str
    exchange: Exchange
    name: str
    product: Product
    size: int
    pricetick: float

    min_volume: float = 1           # minimum trading volume of the contract
    stop_supported: bool = False    # whether server supports stop order
    net_position: bool = False      # whether gateway uses net position volume
    history_data: bool = False      # whether gateway provides bar history data

    option_strike: float = 0
    option_underlying: str = ""     # vt_symbol of underlying contract
    option_type: OptionType = None
    option_expiry: datetime = None

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"


@dataclass
class SubscribeRequest:
    """
    Request sending to specific gateway for subscribing tick data update.
    """

    symbol: str
    exchange: Exchange

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"


@dataclass
class OrderRequest:
    """
    Request sending to specific gateway for creating a new order.
    """

    symbol: str
    exchange: Exchange
    direction: Direction
    type: OrderType
    volume: float
    price: float = 0
    offset: Offset = Offset.NONE

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"

    def create_order_data(self, orderid: str, gateway_name: str):
        """
        Create order data from request.
        """
        order = OrderData(
            symbol=self.symbol,
            exchange=self.exchange,
            orderid=orderid,
            type=self.type,
            direction=self.direction,
            offset=self.offset,
            price=self.price,
            volume=self.volume,
            gateway_name=gateway_name,
        )
        return order


@dataclass
class CancelRequest:
    """
    Request sending to specific gateway for canceling an existing order.
    """

    orderid: str
    symbol: str
    exchange: Exchange

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"


@dataclass
class HistoryRequest:
    """
    Request sending to specific gateway for querying history data.
    """

    symbol: str
    exchange: Exchange
    start: datetime
    end: datetime = None
    interval: Interval = None

    def __post_init__(self):
        """"""
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
